using System;
using System.Linq;
using System.Linq.Expressions;
using NHibernate;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.NHibernate.Aggregates
{
    public static class PredicateConverter
    {
        private static Expression InternalTryConvert<TEntity>(Expression exp)
        {
            var predicateExtracterVisitor = new PredicateConverterVisitor<TEntity>();

            var partialExpression = SubtreeEvaluator.PartialEval(exp);

            predicateExtracterVisitor.Visit(partialExpression);

            if (predicateExtracterVisitor.Expression == null)
                throw new NotSupportedException(String.Format("The expression is not supported: {0} ", exp));

            return predicateExtracterVisitor.Expression;
        }

        private class PredicateConverterVisitor<TEntity> : ExpressionVisitor
        {
            public Expression Expression { get; private set; }

            protected override Expression VisitUnary(UnaryExpression node)
            {
                TryConvert(node.Operand);
                return base.VisitUnary(node);
            }

            protected override Expression VisitLambda<T>(Expression<T> node)
            {
                TryConvert(node.Body);
                return base.VisitLambda(node);
            }

            private void TryConvert(Expression node)
            {
                Expression = Expression ?? node as Expression<Func<TEntity, decimal?>>;
                Expression = Expression ?? node as Expression<Func<TEntity, bool>>;
            }

            protected override Expression VisitConstant(ConstantExpression node)
            {
                TryConvert(node.Value as Expression);
                return base.VisitConstant(node);
            }
        }

        public static Func<IQueryable<TEntity>, object> TryConvert<TEntity>(Expression<Func<IQueryOver<TEntity, TEntity>, IFutureValue<object>>> queryOverfunction)
        {
            Contract.Expect(() => queryOverfunction).IsNotNull()
                .And(() => queryOverfunction.Body as MethodCallExpression)
                .Throw(new ArgumentException("The Expression.Body must be of type MethodCallExpression."))
                .WhenViolate.IsNotNull();

            var errorMsg = String.Format("The expression is not supported: {0} ", queryOverfunction);

            var methodCallExpression = ((MethodCallExpression)queryOverfunction.Body);

            Contract.Expect(() => methodCallExpression.Arguments.Count)
                .Throw(new NotSupportedException(errorMsg))
                .WhenViolate.IsGreatherThanOrEqual(2).IsLessThanOrEqual(3);

            var expressionToMove = InternalTryConvert<TEntity>(methodCallExpression.Arguments[1]);

            var methodName = Reflector.GetMemberName(queryOverfunction);

            try
            {
                switch (methodName)
                {
                    case "Sum":
                        return entities => entities.Sum((Expression<Func<TEntity, decimal?>>)expressionToMove);
                    case "Count":
                        return entities => entities.Count((Expression<Func<TEntity, bool>>)expressionToMove);
                    case "Average":
                        return entities => entities.Average((Expression<Func<TEntity, decimal?>>)expressionToMove);
                    case "Min":
                        return entities => entities.Min((Expression<Func<TEntity, decimal?>>)expressionToMove);
                    case "Max":
                        return entities => entities.Max((Expression<Func<TEntity, decimal?>>)expressionToMove);
                    default:
                        throw new Exception();
                }
            }
            catch
            {
                throw new NotSupportedException(errorMsg);
            }
        }
    }
}